<!DOCTYPE html><html lang="zh-Hans"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><meta name="description" content="vue源码简单解析"><meta name="keywords" content="vue2"><meta name="author" content="晟松"><meta name="copyright" content="晟松"><title>vue源码简单解析 | 晟松</title><link rel="shortcut icon" href="/logo.png"><link rel="stylesheet" href="/css/index.css?version=1.9.0"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@latest/css/font-awesome.min.css?version=1.9.0"><meta name="format-detection" content="telephone=no"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://cdn.jsdelivr.net"><link rel="dns-prefetch" href="https://hm.baidu.com"><script>var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?c34d81d53341febe06a155941e2df16d";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();</script><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><script>var GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"search.xml","languages":{"hits_empty":"找不到您查询的内容:${query}"}},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  hexoVersion: '5.4.0'
} </script><meta name="generator" content="Hexo 5.4.0"></head><body><i class="fa fa-arrow-right" id="toggle-sidebar" aria-hidden="true"></i><div id="sidebar" data-display="true"><div class="toggle-sidebar-info text-center"><span data-toggle="切换文章详情">切换站点概览</span><hr></div><div class="sidebar-toc"><div class="sidebar-toc__title">目录</div><div class="sidebar-toc__progress"><span class="progress-notice">你已经读了</span><span class="progress-num">0</span><span class="progress-percentage">%</span><div class="sidebar-toc__progress-bar"></div></div><div class="sidebar-toc__content"><ol class="toc"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B"><span class="toc-number">1.</span> <span class="toc-text">浏览器渲染过程</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86"><span class="toc-number">2.</span> <span class="toc-text">响应式原理</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#vue%E6%B8%B2%E6%9F%93%E8%BF%87%E7%A8%8B"><span class="toc-number">2.1.</span> <span class="toc-text">vue渲染过程</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#vue2"><span class="toc-number">2.2.</span> <span class="toc-text">vue2</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#vue3"><span class="toc-number">2.3.</span> <span class="toc-text">vue3</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%99%9A%E6%8B%9FDOM"><span class="toc-number">3.</span> <span class="toc-text">虚拟DOM</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E7%AE%80%E4%BB%8B"><span class="toc-number">3.1.</span> <span class="toc-text">简介</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#vue%E4%B8%AD%E7%9A%84%E8%99%9A%E6%8B%9Fdom%E5%AE%9E%E7%8E%B0"><span class="toc-number">3.2.</span> <span class="toc-text">vue中的虚拟dom实现</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#VNode%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="toc-number">3.3.</span> <span class="toc-text">VNode的作用</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#DOM-Diff%E7%AE%97%E6%B3%95"><span class="toc-number">4.</span> <span class="toc-text">DOM-Diff算法</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#patch"><span class="toc-number">4.1.</span> <span class="toc-text">patch</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%9B%B4%E6%96%B0%E5%AD%90%E8%8A%82%E7%82%B9updateChildren"><span class="toc-number">4.2.</span> <span class="toc-text">更新子节点updateChildren()</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%BC%98%E5%8C%96%E6%9B%B4%E6%96%B0%E5%AD%90%E8%8A%82%E7%82%B9-vue2-x"><span class="toc-number">4.3.</span> <span class="toc-text">优化更新子节点(vue2.x)</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%BC%98%E5%8C%96%E6%96%B9%E5%BC%8F-vlue3-x"><span class="toc-number">4.4.</span> <span class="toc-text">优化方式(vlue3.x)</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91"><span class="toc-number">5.</span> <span class="toc-text">模板编译</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%8A%BD%E8%B1%A1%E8%AF%AD%E6%B3%95%E6%A0%91%EF%BC%88AST%EF%BC%89"><span class="toc-number">5.1.</span> <span class="toc-text">抽象语法树（AST）</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%95%B4%E4%BD%93%E8%A7%A3%E6%9E%90%E8%BF%87%E7%A8%8B"><span class="toc-number">5.2.</span> <span class="toc-text">整体解析过程</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#HTML%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">5.3.</span> <span class="toc-text">HTML解析器</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E6%96%87%E6%9C%AC%E8%A7%A3%E6%9E%90%E5%99%A8"><span class="toc-number">5.4.</span> <span class="toc-text">文本解析器</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%BC%98%E5%8C%96%E9%98%B6%E6%AE%B5"><span class="toc-number">5.5.</span> <span class="toc-text">优化阶段</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90%E9%98%B6%E6%AE%B5"><span class="toc-number">5.6.</span> <span class="toc-text">代码生成阶段</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F"><span class="toc-number">6.</span> <span class="toc-text">生命周期</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%88%9D%E5%A7%8B%E9%98%B6%E6%AE%B5-new-Vue"><span class="toc-number">6.1.</span> <span class="toc-text">初始阶段(new Vue)</span></a></li></ol></li></ol></div></div><div class="author-info hide"><div class="author-info__avatar text-center"><img src="/img/avatar.png"></div><div class="author-info__name text-center">晟松</div><div class="author-info__description text-center">晟松的博客</div><div class="follow-button"><a target="_blank" rel="noopener" href="https://gitee.com/cheng_song">Gitee</a></div><hr><div class="author-info-articles"><a class="author-info-articles__archives article-meta" href="/archives"><span class="pull-left">文章</span><span class="pull-right">52</span></a><a class="author-info-articles__tags article-meta" href="/tags"><span class="pull-left">标签</span><span class="pull-right">45</span></a></div></div></div><div id="content-outer"><div id="top-container" style="background-image: url(/img/blog-bg.jpg)"><div id="page-header"><span class="pull-left"> <a id="site-name" href="/">晟松</a></span><i class="fa fa-bars toggle-menu pull-right" aria-hidden="true"></i><span class="pull-right menus">   <a class="site-page" href="/">Home</a><a class="site-page" href="/archives">Archives</a><a class="site-page" href="/tags">Tags</a><a class="site-page" href="/categories">Categories</a></span><span class="pull-right"><a class="site-page social-icon search"><i class="fa fa-search"></i><span> 搜索</span></a></span></div><div id="post-info"><div id="post-title">vue源码简单解析</div><div id="post-meta"><time class="post-meta__date"><i class="fa fa-calendar" aria-hidden="true"></i> 2020-11-16</time><div class="post-meta-wordcount"><span>字数总计: </span><span class="word-count">9.5k</span><span class="post-meta__separator">|</span><span>阅读时长: 36 分钟</span></div></div></div></div><div class="layout" id="content-inner"><article id="post"><div class="article-container" id="post-content"><p>只是一个关于vue2.x版本的核心源码简单学习</p>
<p><a target="_blank" rel="noopener" href="https://vue-js.com/learn-vue/">vue2学习网址</a></p>
<p><a target="_blank" rel="noopener" href="https://vue3js.cn/es6/#%E5%8F%AF%E6%92%A4%E6%B6%88%E7%9A%84proxy">vue3版本（还未完成）</a></p>
<p>之后vue3.x后面再看吧</p>
<h4 id="浏览器渲染过程"><a href="#浏览器渲染过程" class="headerlink" title="浏览器渲染过程"></a>浏览器渲染过程</h4><p><img src="https://upload-images.jianshu.io/upload_images/1959053-7c24fdb60936bd96.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/636/format/webp" alt="img"></p>
<p>第一步，用HTML分析器，分析HTML元素，构建一颗DOM树。</p>
<p>第二步：用CSS分析器，分析CSS文件和元素上的inline样式，生成页面的样式表。</p>
<p>第三步：将上面的DOM树和样式表，关联起来，构建一颗Render树。这一过程又称为Attachment。每个DOM节点都有attach方法，接受样式信息，返回一个render对象（又名renderer）。这些render对象最终会被构建成一颗Render树。</p>
<p>第四步：有了Render树后，浏览器开始布局，会为每个Render树上的节点确定一个在显示屏上出现的精确坐标值。</p>
<p>第五步：Render数有了，节点显示的位置坐标也有了，最后就是调用每个节点的paint方法，让它们显示出来。</p>
<h4 id="响应式原理"><a href="#响应式原理" class="headerlink" title="响应式原理"></a>响应式原理</h4><p>这里在vue2和vue3之间会有一个比较大的区别，都看一下吧</p>
<h5 id="vue渲染过程"><a href="#vue渲染过程" class="headerlink" title="vue渲染过程"></a>vue渲染过程</h5><p>1、编译模板生成<code>render</code>函数，生成<code>vdom</code></p>
<p>2、执行<code>render</code>函数，触发data中的<code>getter</code></p>
<p>3、<code>getter</code>方法收集依赖（通俗点就是,在模板里面触发了哪个变量的<code>getter</code>,就把哪个变量观察起来）</p>
<p>4、在依赖中<code>setter</code>修改data中的数据的时候，<code>Notify</code>看下修改的那个数据是不是之前被观察起来的</p>
<p>5、如果是之前观察起来的，就重新渲染（ <code>re-render</code>），重新生成<code>render</code>函数,生成<code>newVdom</code>形成一个闭环</p>
<h5 id="vue2"><a href="#vue2" class="headerlink" title="vue2"></a>vue2</h5><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// vue2对象响应式原理：Object.defineProperty()</span></span><br><span class="line"><span class="comment">// 没有进行依赖收集</span></span><br><span class="line"><span class="comment">// vue2数组响应式原理：覆盖可以修改数组7个方法</span></span><br><span class="line"><span class="comment">// 从数组原型中获取这7个方法，并覆盖为可以发送更新通知的函数实现</span></span><br><span class="line"><span class="keyword">const</span> originalProto = <span class="built_in">Array</span>.prototype</span><br><span class="line"><span class="keyword">const</span> arrayProto = <span class="built_in">Object</span>.create(originalProto)</span><br><span class="line">[<span class="string">&#x27;push&#x27;</span>, <span class="string">&#x27;pop&#x27;</span>, <span class="string">&#x27;shift&#x27;</span>, <span class="string">&#x27;unshift&#x27;</span>, <span class="string">&#x27;splice&#x27;</span>, <span class="string">&#x27;reverse&#x27;</span>, <span class="string">&#x27;sort&#x27;</span>].forEach(</span><br><span class="line">  <span class="function"><span class="params">method</span> =&gt;</span> &#123;</span><br><span class="line">    arrayProto[method] = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">      <span class="comment">// 做之前的事情</span></span><br><span class="line">      originalProto[method].apply(<span class="built_in">this</span>, <span class="built_in">arguments</span>)</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 通知更新</span></span><br><span class="line">      notifyUpdate()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 思想：递归遍历传入obj，定义每个属性的拦截</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">observe</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> obj !== <span class="string">&#x27;object&#x27;</span> || obj == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> obj</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 判断类型：如果是数组则替换它的原型</span></span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(obj)) &#123;</span><br><span class="line">    <span class="built_in">Object</span>.setPrototypeOf(obj, arrayProto)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> keys = <span class="built_in">Object</span>.keys(obj)</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; keys.length; index++) &#123;</span><br><span class="line">      <span class="keyword">const</span> key = keys[index]</span><br><span class="line">      <span class="comment">// 对obj每个key执行拦截</span></span><br><span class="line">      defineReactive(obj, key, obj[key])</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 具体定义指定的key拦截器</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">defineReactive</span>(<span class="params">obj, key, val</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 递归遍历</span></span><br><span class="line">  observe(val)</span><br><span class="line"></span><br><span class="line">      <span class="comment">// val实际上是一个闭包</span></span><br><span class="line">  <span class="built_in">Object</span>.defineProperty(obj, key, &#123;</span><br><span class="line">    <span class="function"><span class="title">get</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> val</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function"><span class="title">set</span>(<span class="params">newVal</span>)</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (newVal !== val) &#123;</span><br><span class="line">        <span class="comment">// val可能是对象</span></span><br><span class="line">        observe(newVal)</span><br><span class="line">        notifyUpdate()</span><br><span class="line">        val = newVal</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">notifyUpdate</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;页面更新！&#x27;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> data = &#123; <span class="attr">foo</span>: <span class="string">&#x27;foo&#x27;</span>, <span class="attr">bar</span>: &#123; <span class="attr">a</span>: <span class="number">1</span> &#125;, <span class="attr">tua</span>: [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>] &#125;</span><br><span class="line">observe(data)</span><br><span class="line"><span class="comment">// 1.普通更新</span></span><br><span class="line"> data.foo = <span class="string">&#x27;foooooooo&#x27;</span></span><br><span class="line"><span class="comment">// 2.嵌套属性更新</span></span><br><span class="line"><span class="comment">// data.bar.a = 10</span></span><br><span class="line"><span class="comment">// data.dong = &#x27;lalala&#x27; // no ok</span></span><br><span class="line"><span class="comment">// 3.赋值是对象</span></span><br><span class="line"><span class="comment">// data.bar = &#123;a:10&#125;</span></span><br><span class="line"><span class="comment">// 4.数组</span></span><br><span class="line"><span class="comment">// data.tua.push(4)</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 问题分析：</span></span><br><span class="line"><span class="comment">// 1.需要响应化的数据较大，递归遍历性能不好、消耗较大</span></span><br><span class="line"><span class="comment">// 2.新增或删除属性无法监听</span></span><br><span class="line"><span class="comment">// 3.数组响应化需要额外实现</span></span><br><span class="line"><span class="comment">// 4.修改语法有限制</span></span><br></pre></td></tr></table></figure>

<h5 id="vue3"><a href="#vue3" class="headerlink" title="vue3"></a>vue3</h5><figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// vue3响应式原理：利用Proxy对象对数据拦截</span></span><br><span class="line"><span class="keyword">const</span> toProxy = <span class="keyword">new</span> <span class="built_in">WeakMap</span>() <span class="comment">// 形如 obj: observed</span></span><br><span class="line"><span class="keyword">const</span> toRaw = <span class="keyword">new</span> <span class="built_in">WeakMap</span>() <span class="comment">// 形如 observed: obj</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isObject</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">typeof</span> obj === <span class="string">&#x27;object&#x27;</span> || obj === <span class="literal">null</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">hasOwn</span>(<span class="params">obj, key</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> obj.hasOwnProperty(key)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//这里是进行对象响应式的函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">reactive</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!isObject(obj)) &#123;</span><br><span class="line">    <span class="keyword">return</span> obj</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 查找缓存，已经有这个对象的代理</span></span><br><span class="line">  <span class="keyword">if</span> (toProxy.has(obj)) &#123;</span><br><span class="line">    <span class="keyword">return</span> toProxy.get(obj)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 传入obj就是代理对象,此时不用反复代理</span></span><br><span class="line">  <span class="keyword">if</span> (toRaw.has(obj)) &#123;</span><br><span class="line">    <span class="keyword">return</span> obj</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> observed = <span class="keyword">new</span> <span class="built_in">Proxy</span>(obj, &#123;</span><br><span class="line">    <span class="function"><span class="title">get</span>(<span class="params">target, key, receiver</span>)</span> &#123;</span><br><span class="line">      <span class="comment">// 访问</span></span><br><span class="line">      <span class="keyword">const</span> res = <span class="built_in">Reflect</span>.get(target, key, receiver)</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">`获取<span class="subst">$&#123;key&#125;</span>: <span class="subst">$&#123;res&#125;</span>`</span>)</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 依赖收集</span></span><br><span class="line">      track(target, key)</span><br><span class="line">      <span class="comment">//如果是对象，继续进行递归代理，不然直接返回</span></span><br><span class="line">      <span class="keyword">return</span> isObject(res) ? reactive(res) : res</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function"><span class="title">set</span>(<span class="params">target, key, value, receiver</span>)</span> &#123;</span><br><span class="line">      <span class="comment">// 新增和更新</span></span><br><span class="line">      <span class="keyword">const</span> hadKey = hasOwn(target, key) <span class="comment">// ADD 或 SET</span></span><br><span class="line">      <span class="keyword">const</span> oldVal = target[key]</span><br><span class="line">      <span class="keyword">const</span> res = <span class="built_in">Reflect</span>.set(target, key, value, receiver)</span><br><span class="line">      <span class="keyword">if</span> (!hadKey) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`新增<span class="subst">$&#123;key&#125;</span>:<span class="subst">$&#123;value&#125;</span>`</span>)</span><br><span class="line">        trigger(target, <span class="string">&#x27;ADD&#x27;</span>, key)</span><br><span class="line">      &#125; <span class="keyword">else</span> <span class="keyword">if</span> (oldVal !== value) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`设置<span class="subst">$&#123;key&#125;</span>:<span class="subst">$&#123;value&#125;</span>`</span>)</span><br><span class="line">        trigger(target, <span class="string">&#x27;SET&#x27;</span>, key)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> res</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="function"><span class="title">deleteProperty</span>(<span class="params">target, key</span>)</span> &#123;</span><br><span class="line">      <span class="comment">// 删除</span></span><br><span class="line">      <span class="keyword">const</span> hadKey = hasOwn(target, key)</span><br><span class="line">      <span class="keyword">const</span> res = <span class="built_in">Reflect</span>.deleteProperty(target, key)</span><br><span class="line">      <span class="comment">// key存在并且删除成功</span></span><br><span class="line">      <span class="keyword">if</span> (res &amp;&amp; hadKey) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`删除<span class="subst">$&#123;key&#125;</span>:<span class="subst">$&#123;res&#125;</span>`</span>)</span><br><span class="line">        trigger(target, <span class="string">&#x27;DELETE&#x27;</span>, key)</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> res</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">  </span><br><span class="line">  <span class="comment">// 缓存</span></span><br><span class="line">  toProxy.set(obj, observed)</span><br><span class="line">  toRaw.set(observed, obj)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> observed</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> activeReativeEffectStack = []</span><br><span class="line"></span><br><span class="line"><span class="comment">// 依赖收集执行</span></span><br><span class="line"><span class="comment">// target -&gt; desmap -&gt; dep</span></span><br><span class="line"><span class="comment">// 基本结构&#123;target:&#123;key:[eff1，eff2]&#125;&#125;</span></span><br><span class="line"><span class="keyword">let</span> targetsMap = <span class="keyword">new</span> <span class="built_in">WeakMap</span>()</span><br><span class="line"><span class="comment">//get里面的track调用，将此变量被依赖的地方添加进依赖收集存储</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">track</span>(<span class="params">target, key</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 从栈中获取响应函数</span></span><br><span class="line">  <span class="keyword">const</span> effect = activeReativeEffectStack[activeReativeEffectStack.length - <span class="number">1</span>]</span><br><span class="line">  <span class="keyword">if</span> (effect) &#123;</span><br><span class="line">    <span class="comment">//实现基本结构</span></span><br><span class="line">    <span class="keyword">let</span> depsMap = targetsMap.get(target)</span><br><span class="line">    <span class="keyword">if</span> (!depsMap) &#123;</span><br><span class="line">      <span class="comment">// 首次访问target</span></span><br><span class="line">      depsMap = <span class="keyword">new</span> <span class="built_in">Map</span>()</span><br><span class="line">      targetsMap.set(target, depsMap)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存放key</span></span><br><span class="line">    <span class="keyword">let</span> deps = depsMap.get(key)</span><br><span class="line">    <span class="keyword">if</span> (!deps) &#123;</span><br><span class="line">      deps = <span class="keyword">new</span> <span class="built_in">Set</span>()</span><br><span class="line">      depsMap.set(key, deps)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (!deps.has(effect)) &#123;</span><br><span class="line">      deps.add(effect)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//需要进行响应式变化的地方，</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">effect</span>(<span class="params">fn</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 1.异常处理</span></span><br><span class="line">  <span class="comment">// 2.执行函数</span></span><br><span class="line">  <span class="comment">// 3.放置到activeReativeEffectStack</span></span><br><span class="line">  <span class="keyword">const</span> rxEffect = <span class="function"><span class="keyword">function</span>(<span class="params">...args</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      activeReativeEffectStack.push(rxEffect)</span><br><span class="line">      <span class="keyword">return</span> fn(...args) <span class="comment">// 执行函数触发依赖收集</span></span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      activeReativeEffectStack.pop()</span><br><span class="line">    &#125;  </span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  rxEffect() <span class="comment">// 默认立即执行</span></span><br><span class="line">  <span class="keyword">return</span> rxEffect</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 基本结构&#123;target:&#123;key:[eff1，eff2]&#125;&#125;</span></span><br><span class="line"><span class="comment">// 触发target.key对应响应函数，即数据发生了变化，新增或删除这些，进行了监听，并对这些数据进行响应式的变化，执行依赖收集里的函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">trigger</span>(<span class="params">target, type, key</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 获取依赖表</span></span><br><span class="line">  <span class="keyword">const</span> depsMap = targetsMap.get(target)</span><br><span class="line">  <span class="keyword">if</span> (depsMap) &#123;</span><br><span class="line">    <span class="comment">// 获取响应函数集合</span></span><br><span class="line">    <span class="keyword">const</span> deps = depsMap.get(key)</span><br><span class="line">    <span class="keyword">const</span> effects = <span class="keyword">new</span> <span class="built_in">Set</span>()</span><br><span class="line">    <span class="keyword">if</span> (deps) &#123;</span><br><span class="line">      <span class="comment">// 获取所有响应函数</span></span><br><span class="line">      deps.forEach(<span class="function"><span class="params">effect</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// effect()</span></span><br><span class="line">        effects.add(effect)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 数组新增或删除</span></span><br><span class="line">    <span class="keyword">if</span> (type === <span class="string">&#x27;ADD&#x27;</span> || type === <span class="string">&#x27;DELETE&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(target)) &#123;</span><br><span class="line">        <span class="keyword">const</span> deps = depsMap.get(<span class="string">&#x27;length&#x27;</span>)</span><br><span class="line">        <span class="keyword">if</span> (deps) &#123;</span><br><span class="line">          deps.forEach(<span class="function"><span class="params">effect</span> =&gt;</span> &#123;</span><br><span class="line">            effects.add(effect)</span><br><span class="line">          &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取已存在的Dep Set执行</span></span><br><span class="line">    effects.forEach(<span class="function"><span class="params">effect</span> =&gt;</span> effect())</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> data = &#123; <span class="attr">foo</span>: <span class="string">&#x27;foo&#x27;</span>, <span class="attr">bar</span>: &#123; <span class="attr">a</span>: <span class="number">1</span> &#125; &#125;</span><br><span class="line"><span class="keyword">const</span> react = reactive(data)</span><br><span class="line"><span class="comment">// 1.获取</span></span><br><span class="line"><span class="comment">// react.foo // ok</span></span><br><span class="line"><span class="comment">// 2.设置已存在属性</span></span><br><span class="line"><span class="comment">// react.foo = &#x27;foooooooo&#x27;</span></span><br><span class="line"><span class="comment">// 3.设置不存在属性</span></span><br><span class="line"><span class="comment">// react.baz = &#x27;bazzzzzz&#x27;</span></span><br><span class="line"><span class="comment">// 4.嵌套对象</span></span><br><span class="line"><span class="comment">// react.bar.a = 10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 避免重复代理</span></span><br><span class="line"><span class="comment">// console.log(reactive(data) === react) // true</span></span><br><span class="line"><span class="comment">// reactive(react)</span></span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">&#x27;count发生了变化1：&#x27;</span>, react.foo)</span><br><span class="line"><span class="comment">//在get中收集依赖,在set中触发依赖</span></span><br><span class="line">effect(<span class="function">() =&gt;</span> &#123;</span><br><span class="line"><span class="comment">//这里通过react.foo触发了get，而get就根据这个key触发track进行这个key变量的依赖跟踪，因为这个回调函数已经在effect里面缓存到一个数组activeReativeEffectStack里面，</span></span><br><span class="line"><span class="comment">//所以在track函数里面就获取这个回调函数，然后判断effect的基本结构里面这个key对应的结构里面是否已经包含了这个方法，即是否已经进行了依赖收集，</span></span><br><span class="line"><span class="comment">//如果不存在就添加进去</span></span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;count发生了变化2：&#x27;</span>, react.foo)</span><br><span class="line">  <span class="comment">// dom</span></span><br><span class="line">&#125;)</span><br><span class="line">react.foo = <span class="string">&#x27;fooooooo&#x27;</span></span><br></pre></td></tr></table></figure>





<p>上面的都只是一个简单实现，真正的源码还是要复杂的多，特别是对依赖收集和依赖触发这一个阶段的改变</p>
<h4 id="虚拟DOM"><a href="#虚拟DOM" class="headerlink" title="虚拟DOM"></a>虚拟DOM</h4><h5 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h5><p>所谓虚拟DOM，就是用一个<code>JS</code>对象来描述一个<code>DOM</code>节点，像如下示例：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">&lt;div <span class="class"><span class="keyword">class</span></span>=<span class="string">&quot;a&quot;</span> id=<span class="string">&quot;b&quot;</span>&gt;我是内容&lt;/div&gt;</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">tag</span>:<span class="string">&#x27;div&#x27;</span>,        <span class="comment">// 元素标签</span></span><br><span class="line">  <span class="attr">attrs</span>:&#123;           <span class="comment">// 属性</span></span><br><span class="line">    <span class="attr">class</span>:<span class="string">&#x27;a&#x27;</span>,</span><br><span class="line">    <span class="attr">id</span>:<span class="string">&#x27;b&#x27;</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">text</span>:<span class="string">&#x27;我是内容&#x27;</span>,  <span class="comment">// 文本内容</span></span><br><span class="line">  <span class="attr">children</span>:[]       <span class="comment">// 子元素</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们把组成一个<code>DOM</code>节点的必要东西通过一个<code>JS</code>对象表示出来，那么这个<code>JS</code>对象就可以用来描述这个<code>DOM</code>节点，我们把这个<code>JS</code>对象就称为是这个真实<code>DOM</code>节点的虚拟<code>DOM</code>节点。</p>
<p>我们可以用<code>JS</code>模拟出一个<code>DOM</code>节点，称之为虚拟<code>DOM</code>节点。当数据发生变化时，我们对比变化前后的虚拟<code>DOM</code>节点，通过<code>DOM-Diff</code>算法计算出需要更新的地方，然后去更新需要更新的视图。</p>
<p>这就是虚拟<code>DOM</code>产生的原因以及最大的用途</p>
<h5 id="vue中的虚拟dom实现"><a href="#vue中的虚拟dom实现" class="headerlink" title="vue中的虚拟dom实现"></a>vue中的虚拟dom实现</h5><p>首先会有一个Vnode类，通过这个类可以实现各种的dom节点</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源码位置：src/core/vdom/vnode.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="class"><span class="keyword">class</span> <span class="title">VNode</span> </span>&#123;</span><br><span class="line">  <span class="title">constructor</span> (<span class="params"></span></span><br><span class="line"><span class="params">    tag?: string,</span></span><br><span class="line"><span class="params">    data?: VNodeData,</span></span><br><span class="line"><span class="params">    children?: ?<span class="built_in">Array</span>&lt;VNode&gt;,</span></span><br><span class="line"><span class="params">    text?: string,</span></span><br><span class="line"><span class="params">    elm?: Node,</span></span><br><span class="line"><span class="params">    context?: Component,</span></span><br><span class="line"><span class="params">    componentOptions?: VNodeComponentOptions,</span></span><br><span class="line"><span class="params">    asyncFactory?: <span class="built_in">Function</span></span></span><br><span class="line"><span class="params">  </span>) &#123;</span><br><span class="line">    <span class="built_in">this</span>.tag = tag                                <span class="comment">/*当前节点的标签名*/</span></span><br><span class="line">    <span class="built_in">this</span>.data = data        <span class="comment">/*当前节点对应的对象，包含了具体的一些数据信息，是一个VNodeData类型，可以参考VNodeData类型中的数据信息*/</span></span><br><span class="line">    <span class="built_in">this</span>.children = children  <span class="comment">/*当前节点的子节点，是一个数组*/</span></span><br><span class="line">    <span class="built_in">this</span>.text = text     <span class="comment">/*当前节点的文本*/</span></span><br><span class="line">    <span class="built_in">this</span>.elm = elm       <span class="comment">/*当前虚拟节点对应的真实dom节点*/</span></span><br><span class="line">    <span class="built_in">this</span>.ns = <span class="literal">undefined</span>            <span class="comment">/*当前节点的名字空间*/</span></span><br><span class="line">    <span class="built_in">this</span>.context = context          <span class="comment">/*当前组件节点对应的Vue实例*/</span></span><br><span class="line">    <span class="built_in">this</span>.fnContext = <span class="literal">undefined</span>       <span class="comment">/*函数式组件对应的Vue实例*/</span></span><br><span class="line">    <span class="built_in">this</span>.fnOptions = <span class="literal">undefined</span></span><br><span class="line">    <span class="built_in">this</span>.fnScopeId = <span class="literal">undefined</span></span><br><span class="line">    <span class="built_in">this</span>.key = data &amp;&amp; data.key           <span class="comment">/*节点的key属性，被当作节点的标志，用以优化*/</span></span><br><span class="line">    <span class="built_in">this</span>.componentOptions = componentOptions   <span class="comment">/*组件的option选项*/</span></span><br><span class="line">    <span class="built_in">this</span>.componentInstance = <span class="literal">undefined</span>       <span class="comment">/*当前节点对应的组件的实例*/</span></span><br><span class="line">    <span class="built_in">this</span>.parent = <span class="literal">undefined</span>           <span class="comment">/*当前节点的父节点*/</span></span><br><span class="line">    <span class="built_in">this</span>.raw = <span class="literal">false</span>         <span class="comment">/*简而言之就是是否为原生HTML或只是普通文本，innerHTML的时候为true，textContent的时候为false*/</span></span><br><span class="line">    <span class="built_in">this</span>.isStatic = <span class="literal">false</span>         <span class="comment">/*静态节点标志*/</span></span><br><span class="line">    <span class="built_in">this</span>.isRootInsert = <span class="literal">true</span>      <span class="comment">/*是否作为跟节点插入*/</span></span><br><span class="line">    <span class="built_in">this</span>.isComment = <span class="literal">false</span>             <span class="comment">/*是否为注释节点*/</span></span><br><span class="line">    <span class="built_in">this</span>.isCloned = <span class="literal">false</span>           <span class="comment">/*是否为克隆节点*/</span></span><br><span class="line">    <span class="built_in">this</span>.isOnce = <span class="literal">false</span>                <span class="comment">/*是否有v-once指令*/</span></span><br><span class="line">    <span class="built_in">this</span>.asyncFactory = asyncFactory</span><br><span class="line">    <span class="built_in">this</span>.asyncMeta = <span class="literal">undefined</span></span><br><span class="line">    <span class="built_in">this</span>.isAsyncPlaceholder = <span class="literal">false</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  get child (): Component | <span class="keyword">void</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.componentInstance</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>具体的定义还是看官网源码</p>
<p>可以通过<strong>不同属性的搭配</strong>，可以描述出以下几种类型的节点。</p>
<ul>
<li>注释节点</li>
<li>文本节点</li>
<li>元素节点</li>
<li>组件节点</li>
<li>函数式组件节点</li>
<li>克隆节点</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 注释节点</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createEmptyVNode = <span class="function">(<span class="params">text: string = <span class="string">&#x27;&#x27;</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> node = <span class="keyword">new</span> VNode()</span><br><span class="line">  node.text = text</span><br><span class="line">  node.isComment = <span class="literal">true</span></span><br><span class="line">  <span class="keyword">return</span> node</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 文本节点</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">createTextVNode</span> (<span class="params">val: string | number</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> VNode(<span class="literal">undefined</span>, <span class="literal">undefined</span>, <span class="literal">undefined</span>, <span class="built_in">String</span>(val))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 克隆节点</span></span><br><span class="line"><span class="comment">// 克隆节点就是把已有节点的属性全部复制到新节点中，而现有节点和新克隆得到的节点之间唯一的不同就是克隆得到的节点isCloned为true</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">cloneVNode</span> (<span class="params">vnode: VNode</span>): <span class="title">VNode</span> </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> cloned = <span class="keyword">new</span> VNode(</span><br><span class="line">    vnode.tag,</span><br><span class="line">    vnode.data,</span><br><span class="line">    <span class="comment">// #7975</span></span><br><span class="line">    <span class="comment">// clone children array to avoid mutating original in case of cloning</span></span><br><span class="line">    <span class="comment">// a child.</span></span><br><span class="line">    vnode.children &amp;&amp; vnode.children.slice(),</span><br><span class="line">    vnode.text,</span><br><span class="line">    vnode.elm,</span><br><span class="line">    vnode.context,</span><br><span class="line">    vnode.componentOptions,</span><br><span class="line">    vnode.asyncFactory</span><br><span class="line">  )</span><br><span class="line">  cloned.ns = vnode.ns</span><br><span class="line">  cloned.isStatic = vnode.isStatic</span><br><span class="line">  cloned.key = vnode.key</span><br><span class="line">  cloned.isComment = vnode.isComment</span><br><span class="line">  cloned.fnContext = vnode.fnContext</span><br><span class="line">  cloned.fnOptions = vnode.fnOptions</span><br><span class="line">  cloned.fnScopeId = vnode.fnScopeId</span><br><span class="line">  cloned.asyncMeta = vnode.asyncMeta</span><br><span class="line">  cloned.isCloned = <span class="literal">true</span></span><br><span class="line">  <span class="keyword">return</span> cloned</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>元素节点</strong></p>
<p>相比之下，元素节点更贴近于我们通常看到的真实<code>DOM</code>节点，它有描述节点标签名词的<code>tag</code>属性，描述节点属性如<code>class</code>、<code>attributes</code>等的<code>data</code>属性，有描述包含的子节点信息的<code>children</code>属性等。由于元素节点所包含的情况相比而言比较复杂，源码中没有像前三种节点一样直接写死（当然也不可能写死），那就举个简单例子说明一下：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 真实DOM节点</span></span><br><span class="line">&lt;div id=<span class="string">&#x27;a&#x27;</span>&gt;<span class="xml"><span class="tag">&lt;<span class="name">span</span>&gt;</span>难凉热血<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span>&lt;/div&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// VNode节点</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">tag</span>:<span class="string">&#x27;div&#x27;</span>,</span><br><span class="line">  <span class="attr">data</span>:&#123;&#125;,</span><br><span class="line">  <span class="attr">children</span>:[</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">tag</span>:<span class="string">&#x27;span&#x27;</span>,</span><br><span class="line">      <span class="attr">text</span>:<span class="string">&#x27;难凉热血&#x27;</span></span><br><span class="line">    &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们可以看到，真实<code>DOM</code>节点中:<code>div</code>标签里面包含了一个<code>span</code>标签，而<code>span</code>标签里面有一段文本。反应到<code>VNode</code>节点上就如上所示:<code>tag</code>表示标签名，<code>data</code>表示标签的属性<code>id</code>等，<code>children</code>表示子节点数组。</p>
<p> <strong>组件节点</strong></p>
<p>组件节点除了有元素节点具有的属性之外，它还有两个特有的属性：</p>
<ul>
<li>componentOptions :组件的option选项，如组件的<code>props</code>等</li>
<li>componentInstance :当前组件节点对应的<code>Vue</code>实例</li>
</ul>
<p><strong>函数式组件节点</strong></p>
<p>函数式组件节点相较于组件节点，它又有两个特有的属性：</p>
<ul>
<li>fnContext:函数式组件对应的Vue实例</li>
<li>fnOptions: 组件的option选项</li>
</ul>
<p>以上就是<code>VNode</code>可以描述的多种节点类型，它们本质上都是<code>VNode</code>类的实例，只是在实例化的时候传入的属性参数不同而已。</p>
<h5 id="VNode的作用"><a href="#VNode的作用" class="headerlink" title="VNode的作用"></a>VNode的作用</h5><p>说了这么多，那么<code>VNode</code>在<code>Vue</code>的整个虚拟<code>DOM</code>过程起了什么作用呢？</p>
<p>其实<code>VNode</code>的作用是相当大的。我们在视图渲染之前，把写好的<code>template</code>模板先编译成<code>VNode</code>并缓存下来，等到数据发生变化页面需要重新渲染的时候，我们把数据发生变化后生成的<code>VNode</code>与前一次缓存下来的<code>VNode</code>进行对比，找出差异，然后有差异的<code>VNode</code>对应的真实<code>DOM</code>节点就是需要重新渲染的节点，最后根据有差异的<code>VNode</code>创建出真实的<code>DOM</code>节点再插入到视图中，最终完成一次视图更新</p>
<h4 id="DOM-Diff算法"><a href="#DOM-Diff算法" class="headerlink" title="DOM-Diff算法"></a>DOM-Diff算法</h4><p><a target="_blank" rel="noopener" href="http://www.cpengx.cn/p/561.html">老陈文档</a></p>
<p><code>VNode</code>最大的用途就是在数据变化前后生成真实<code>DOM</code>对应的虚拟<code>DOM</code>节点，然后就可以对比新旧两份<code>VNode</code>，找出差异所在，然后更新有差异的<code>DOM</code>节点，最终达到以最少操作真实<code>DOM</code>更新视图的目的。而对比新旧两份<code>VNode</code>并找出差异的过程就是所谓的<code>DOM-Diff</code>过程。<code>DOM-Diff</code>算法是整个虚拟<code>DOM</code>的核心所在</p>
<h5 id="patch"><a href="#patch" class="headerlink" title="patch"></a><strong>patch</strong></h5><p>在<code>Vue</code>中，把 <code>DOM-Diff</code>过程叫做<code>patch</code>过程。patch,意为“补丁”，即指对旧的<code>VNode</code>修补，打补丁从而得到新的<code>VNode</code>，非常形象哈。那不管叫什么，其本质都是把对比新旧两份<code>VNode</code>的过程。我们在下面研究<code>patch</code>过程的时候，一定把握住这样一个思想：所谓旧的<code>VNode</code>(即<code>oldVNode</code>)就是数据变化之前视图所对应的虚拟<code>DOM</code>节点，而新的<code>VNode</code>是数据变化之后将要渲染的新的视图所对应的虚拟<code>DOM</code>节点，所以我们要以生成的新的<code>VNode</code>为基准，对比旧的<code>oldVNode</code>，如果新的<code>VNode</code>上有的节点而旧的<code>oldVNode</code>上没有，那么就在旧的<code>oldVNode</code>上加上去；如果新的<code>VNode</code>上没有的节点而旧的<code>oldVNode</code>上有，那么就在旧的<code>oldVNode</code>上去掉；如果某些节点在新的<code>VNode</code>和旧的<code>oldVNode</code>上都有，那么就以新的<code>VNode</code>为准，更新旧的<code>oldVNode</code>，从而让新旧<code>VNode</code>相同。最后实现最少量的改变DOM，节约资源</p>
<ul>
<li>创建节点：新的<code>VNode</code>中有而旧的<code>oldVNode</code>中没有，就在旧的<code>oldVNode</code>中创建。</li>
<li>删除节点：新的<code>VNode</code>中没有而旧的<code>oldVNode</code>中有，就从旧的<code>oldVNode</code>中删除。</li>
<li>更新节点：新的<code>VNode</code>和旧的<code>oldVNode</code>中都有，就以新的<code>VNode</code>为准，更新旧的<code>oldVNode</code>。</li>
</ul>
<p><strong>创建节点</strong></p>
<p>实际上只有3种类型的节点能够被创建并插入到<code>DOM</code>中，它们分别是：元素节点、文本节点、注释节点。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源码位置: /src/core/vdom/patch.js</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">createElm</span> (<span class="params">vnode, parentElm, refElm</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> data = vnode.data</span><br><span class="line">    <span class="keyword">const</span> children = vnode.children</span><br><span class="line">    <span class="keyword">const</span> tag = vnode.tag</span><br><span class="line">    <span class="keyword">if</span> (isDef(tag)) &#123;</span><br><span class="line">        <span class="comment">// nodeOps.createElement是vue为了兼容性封装的一个创建节点的函数，在浏览器端等同于document.createTextNode()</span></span><br><span class="line">      	vnode.elm = nodeOps.createElement(tag, vnode)   <span class="comment">// 创建元素节点</span></span><br><span class="line">        createChildren(vnode, children, insertedVnodeQueue) <span class="comment">// 创建元素节点的子节点</span></span><br><span class="line">        insert(parentElm, vnode.elm, refElm)       <span class="comment">// 插入到DOM中</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isTrue(vnode.isComment)) &#123;</span><br><span class="line">      vnode.elm = nodeOps.createComment(vnode.text)  <span class="comment">// 创建注释节点</span></span><br><span class="line">      insert(parentElm, vnode.elm, refElm)           <span class="comment">// 插入到DOM中</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      vnode.elm = nodeOps.createTextNode(vnode.text)  <span class="comment">// 创建文本节点</span></span><br><span class="line">      insert(parentElm, vnode.elm, refElm)           <span class="comment">// 插入到DOM中</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><strong>删除节点</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">removeNode</span> (<span class="params">el</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> parent = nodeOps.parentNode(el)  <span class="comment">// 获取父节点</span></span><br><span class="line">    <span class="keyword">if</span> (isDef(parent)) &#123;</span><br><span class="line">      nodeOps.removeChild(parent, el)  <span class="comment">// 调用父节点的removeChild方法</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p><strong>更新节点</strong>（重点）</p>
<p>更新节点就是当某些节点在新的<code>VNode</code>和旧的<code>oldVNode</code>中都有时，我们就需要细致比较一下，找出不一样的地方进行更新</p>
<p>更新节点的时候我们需要对以下3种情况进行判断并分别处理（是更新不是删除、增加）：</p>
<ol>
<li><p>如果<code>VNode</code>和<code>oldVNode</code>均为静态节点</p>
<p>我们说了，静态节点无论数据发生任何变化都与它无关，所以都为静态节点的话则直接跳过，无需处理。</p>
</li>
<li><p>如果<code>VNode</code>是文本节点</p>
<p>如果<code>VNode</code>是文本节点即表示这个节点内只包含纯文本，那么只需看<code>oldVNode</code>是否也是文本节点，如果是，那就比较两个文本是否不同，如果不同则把<code>oldVNode</code>里的文本改成跟<code>VNode</code>的文本一样。如果<code>oldVNode</code>不是文本节点，那么不论它是什么，直接调用<code>setTextNode</code>方法把它改成文本节点，并且文本内容跟<code>VNode</code>相同。</p>
</li>
<li><p>如果<code>VNode</code>是元素节点</p>
<p>如果<code>VNode</code>是元素节点，则又细分以下两种情况：</p>
<ul>
<li><p>该节点包含子节点</p>
<p>如果新的节点内包含了子节点，那么此时要看旧的节点是否包含子节点，如果旧的节点里也包含了子节点，那就需要递归对比更新子节点；如果旧的节点里不包含子节点，那么这个旧节点有可能是空节点或者是文本节点，如果旧的节点是空节点就把新的节点里的子节点创建一份然后插入到旧的节点里面，如果旧的节点是文本节点，则把文本清空，然后把新的节点里的子节点创建一份然后插入到旧的节点里面。</p>
</li>
<li><p>该节点不包含子节点</p>
<p>如果该节点不包含子节点，同时它又不是文本节点，那就说明该节点是个空节点，那就好办了，不管旧节点之前里面都有啥，直接清空即可。</p>
</li>
</ul>
</li>
</ol>
<p><strong>源码</strong></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 更新节点</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">patchVnode</span> (<span class="params">oldVnode, vnode, insertedVnodeQueue, removeOnly</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// vnode与oldVnode是否完全一样？若是，退出程序</span></span><br><span class="line">  <span class="keyword">if</span> (oldVnode === vnode) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> elm = vnode.elm = oldVnode.elm <span class="comment">// elm为当前虚拟节点对应的真实dom节点</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// vnode与oldVnode是否都是静态节点？若是，退出程序</span></span><br><span class="line">  <span class="keyword">if</span> (isTrue(vnode.isStatic) &amp;&amp;</span><br><span class="line">    isTrue(oldVnode.isStatic) &amp;&amp;</span><br><span class="line">    vnode.key === oldVnode.key &amp;&amp;</span><br><span class="line">    (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))</span><br><span class="line">  ) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> oldCh = oldVnode.children</span><br><span class="line">  <span class="keyword">const</span> ch = vnode.children</span><br><span class="line">  <span class="comment">// vnode有text属性？若没有：</span></span><br><span class="line">  <span class="keyword">if</span> (isUndef(vnode.text)) &#123;</span><br><span class="line">    <span class="comment">// vnode的子节点与oldVnode的子节点是否都存在？</span></span><br><span class="line">    <span class="keyword">if</span> (isDef(oldCh) &amp;&amp; isDef(ch)) &#123;</span><br><span class="line">      <span class="comment">// 若都存在，判断子节点是否相同，不同则更新子节点</span></span><br><span class="line">      <span class="comment">// 注意这个updateChildren函数，是diff的核心</span></span><br><span class="line">      <span class="keyword">if</span> (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 若只有vnode的子节点存在</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (isDef(ch)) &#123;</span><br><span class="line">      <span class="comment">/**</span></span><br><span class="line"><span class="comment">       * 判断oldVnode是否有文本？</span></span><br><span class="line"><span class="comment">       * 若没有，则把vnode的子节点添加到真实DOM中</span></span><br><span class="line"><span class="comment">       * 若有，则清空Dom中的文本，再把vnode的子节点添加到真实DOM中</span></span><br><span class="line"><span class="comment">       */</span></span><br><span class="line">      <span class="keyword">if</span> (isDef(oldVnode.text)) nodeOps.setTextContent(elm, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">      addVnodes(elm, <span class="literal">null</span>, ch, <span class="number">0</span>, ch.length - <span class="number">1</span>, insertedVnodeQueue)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 若只有oldnode的子节点存在</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (isDef(oldCh)) &#123;</span><br><span class="line">      <span class="comment">// 清空DOM中的子节点</span></span><br><span class="line">      removeVnodes(elm, oldCh, <span class="number">0</span>, oldCh.length - <span class="number">1</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 若vnode和oldnode都没有子节点，但是oldnode中有文本</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (isDef(oldVnode.text)) &#123;</span><br><span class="line">      <span class="comment">// 清空oldnode文本</span></span><br><span class="line">      nodeOps.setTextContent(elm, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 上面两个判断一句话概括就是，如果vnode中既没有text，也没有子节点，那么对应的oldnode中有什么就清空什么</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 若有，vnode的text属性与oldVnode的text属性是否相同？</span></span><br><span class="line">  <span class="keyword">else</span> <span class="keyword">if</span> (oldVnode.text !== vnode.text) &#123;</span><br><span class="line">    <span class="comment">// 若不相同：则用vnode的text替换真实DOM的文本</span></span><br><span class="line">    nodeOps.setTextContent(elm, vnode.text)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src="https://vue-js.com/learn-vue/assets/img/3.7b0442aa.png" alt="img" style="zoom:67%;" />

<h5 id="更新子节点updateChildren"><a href="#更新子节点updateChildren" class="headerlink" title="更新子节点updateChildren()"></a><strong>更新子节点updateChildren()</strong></h5><p>当新的<code>VNode</code>与旧的<code>oldVNode</code>都是元素节点并且都包含子节点时，那么这两个节点的<code>VNode</code>实例上的<code>children</code>属性就是所包含的子节点数组。我们把新的<code>VNode</code>上的子节点数组记为<code>newChildren</code>，把旧的<code>oldVNode</code>上的子节点数组记为<code>oldChildren</code>，我们把<code>newChildren</code>里面的元素与<code>oldChildren</code>里的元素一一进行对比，对比两个子节点数组肯定是要通过循环，外层循环<code>newChildren</code>数组，内层循环<code>oldChildren</code>数组，每循环外层<code>newChildren</code>数组里的一个子节点，就去内层<code>oldChildren</code>数组里找看有没有与之相同的子节点</p>
<p>以上这个过程将会存在以下四种情况：</p>
<ul>
<li><p>创建子节点</p>
<p>如果<code>newChildren</code>里面的某个子节点在<code>oldChildren</code>里找不到与之相同的子节点，那么说明<code>newChildren</code>里面的这个子节点是之前没有的，是需要此次新增的节点，那么就创建子节点。<strong>创建后节点合适的位置是所有未处理节点之前，而并非所有已处理节点之后</strong></p>
</li>
<li><p>删除子节点</p>
<p>如果把<code>newChildren</code>里面的每一个子节点都循环完毕后，发现在<code>oldChildren</code>还有未处理的子节点，那就说明这些未处理的子节点是需要被废弃的，那么就将这些节点删除。</p>
</li>
<li><p>移动子节点</p>
<p>如果<code>newChildren</code>里面的某个子节点在<code>oldChildren</code>里找到了与之相同的子节点，但是所处的位置不同，这说明此次变化需要调整该子节点的位置，那就以<code>newChildren</code>里子节点的位置为基准，调整<code>oldChildren</code>里该节点的位置，使之与在<code>newChildren</code>里的位置相同。<strong>所有未处理节点之前就是我们要移动的目的位置</strong></p>
<img src="https://vue-js.com/learn-vue/assets/img/6.b9621b4d.png" style="zoom:67%;" /></li>
<li><p>更新节点</p>
<p>如果<code>newChildren</code>里面的某个子节点在<code>oldChildren</code>里找到了与之相同的子节点，并且所处的位置也相同，那么就更新<code>oldChildren</code>里该节点，使之与<code>newChildren</code>里的该节点相同。</p>
</li>
</ul>
<h5 id="优化更新子节点-vue2-x"><a href="#优化更新子节点-vue2-x" class="headerlink" title="优化更新子节点(vue2.x)"></a>优化更新子节点(vue2.x)</h5><p>主要是上面的新旧children节点的比较的N^2的双重循环时间复杂度有些高</p>
<p><strong>这里是优先处理一些特殊情况</strong></p>
<p>其实我们可以这样想，我们不要按顺序去循环<code>newChildren</code>和<code>oldChildren</code>这两个数组，可以先比较这两个数组里特殊位置的子节点，比如：</p>
<ul>
<li>先把<code>newChildren</code>数组里的所有未处理子节点的第一个子节点和<code>oldChildren</code>数组里所有未处理子节点的第一个子节点做比对，如果相同，那就直接进入更新节点的操作；</li>
<li>如果不同，再把<code>newChildren</code>数组里所有未处理子节点的最后一个子节点和<code>oldChildren</code>数组里所有未处理子节点的最后一个子节点做比对，如果相同，那就直接进入更新节点的操作；</li>
<li>如果不同，再把<code>newChildren</code>数组里所有未处理子节点的最后一个子节点和<code>oldChildren</code>数组里所有未处理子节点的第一个子节点做比对，如果相同，那就直接进入更新节点的操作，更新完后再将<code>oldChildren</code>数组里的该节点<strong>移动</strong>到与<code>newChildren</code>数组里节点相同的位置（我们要把<code>oldChildren</code>数组里把第一个子节点移动到数组中<strong>所有未处理节点之后</strong>）；</li>
<li>如果不同，再把<code>newChildren</code>数组里所有未处理子节点的第一个子节点和<code>oldChildren</code>数组里所有未处理子节点的最后一个子节点做比对，如果相同，那就直接进入更新节点的操作，更新完后再将<code>oldChildren</code>数组里的该节点<strong>移动</strong>到与<code>newChildren</code>数组里节点相同的位置（我们要把<code>oldChildren</code>数组里把最后一个子节点移动到数组中<strong>所有未处理节点之前</strong>）；</li>
<li>最后四种情况都试完如果还不同，那就<strong>按照之前循环</strong>的方式来查找节点。</li>
</ul>
<p>其过程如下图所示：</p>
 <img src="https://vue-js.com/learn-vue/assets/img/8.e4c85c40.png" alt="img" style="zoom:67%;" />

<p>具体的方式还是看源码吧</p>
<h5 id="优化方式-vlue3-x"><a href="#优化方式-vlue3-x" class="headerlink" title="优化方式(vlue3.x)"></a><a target="_blank" rel="noopener" href="https://juejin.cn/post/6940630038983213087">优化方式(vlue3.x)</a></h5><h4 id="模板编译"><a href="#模板编译" class="headerlink" title="模板编译"></a>模板编译</h4><p>把用户写的模板进行编译，就会产生<code>VNode</code>，得到虚拟DOM</p>
<img src="https://img-blog.csdnimg.cn/20200419204951458.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjEyNDIxNA==,size_16,color_FFFFFF,t_70" style="zoom:67%;" />

<p><code>Vue</code>会把用户在<code>&lt;template&gt;&lt;/template&gt;</code>标签中写的类似于原生<code>HTML</code>的内容进行编译，把原生<code>HTML</code>的内容找出来，再把非原生<code>HTML</code>找出来，经过一系列的逻辑处理生成渲染函数，也就是<code>render</code>函数，而<code>render</code>函数会将模板内容生成对应的<code>VNode</code>，而<code>VNode</code>再经过前几篇文章介绍的<code>patch</code>过程从而得到将要渲染的视图中的<code>VNode</code>，最后根据<code>VNode</code>创建真实的<code>DOM</code>节点并插入到视图中， 最终完成视图的渲染更新</p>
<img src="https://vue-js.com/learn-vue/assets/img/1.f0570125.png" alt="img" style="zoom:67%;" />

<h5 id="抽象语法树（AST）"><a href="#抽象语法树（AST）" class="headerlink" title="抽象语法树（AST）"></a><strong>抽象语法树</strong>（AST）</h5><p>是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构，树上的每个节点都表示源代码中的一种结构。</p>
<img src="https://vue-js.com/learn-vue/assets/img/2.5596631a.png" alt="img" style="zoom:80%;" />

<p>将一堆字符串模板解析成抽象语法树<code>AST</code>后，我们就可以对其进行各种操作处理了，处理完后用处理后的<code>AST</code>来生成<code>render</code>函数（提一下，render函数执行后就会得到一个虚拟节点树）。其具体流程可大致分为三个阶段：</p>
<ol>
<li>模板解析阶段：将一堆模板字符串用正则等方式解析成抽象语法树<code>AST</code>；</li>
<li>优化阶段：遍历<code>AST</code>，找出其中的静态节点，并打上标记；</li>
<li>代码生成阶段：将<code>AST</code>转换成渲染函数</li>
</ol>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 源码位置: /src/complier/index.js	总体解析过程</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> createCompiler = createCompilerCreator(<span class="function"><span class="keyword">function</span> <span class="title">baseCompile</span> (<span class="params"></span></span></span><br><span class="line"><span class="params"><span class="function">  template: string,</span></span></span><br><span class="line"><span class="params"><span class="function">  options: CompilerOptions</span></span></span><br><span class="line"><span class="params"><span class="function"></span>): <span class="title">CompiledResult</span> </span>&#123;</span><br><span class="line">  <span class="comment">// 模板解析阶段：用正则等方式解析 template 模板中的指令、class、style等数据，形成AST</span></span><br><span class="line">  <span class="keyword">const</span> ast = parse(template.trim(), options)</span><br><span class="line">  <span class="keyword">if</span> (options.optimize !== <span class="literal">false</span>) &#123;</span><br><span class="line">    <span class="comment">// 优化阶段：遍历AST，找出其中的静态节点，并打上标记；当patch的时候就跳过静态节点，优化性能</span></span><br><span class="line">    optimize(ast, options)</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 代码生成阶段：将AST转换成渲染函数；</span></span><br><span class="line">  <span class="keyword">const</span> code = generate(ast, options)</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    ast,</span><br><span class="line">    <span class="attr">render</span>: code.render,</span><br><span class="line">    <span class="attr">staticRenderFns</span>: code.staticRenderFns</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h5 id="整体解析过程"><a href="#整体解析过程" class="headerlink" title="整体解析过程"></a>整体解析过程</h5><p>把用户所写的模板根据一定的解析规则解析出有效的信息，最后用这些信息形成<code>AST</code></p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 代码位置：/src/complier/parser/index.js</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Convert HTML string to AST.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parse</span>(<span class="params">template, options</span>) </span>&#123;</span><br><span class="line">   <span class="comment">// HTML解析器</span></span><br><span class="line">  parseHTML(template, &#123;</span><br><span class="line">    warn,</span><br><span class="line">    <span class="attr">expectHTML</span>: options.expectHTML,</span><br><span class="line">    <span class="attr">isUnaryTag</span>: options.isUnaryTag,</span><br><span class="line">    <span class="attr">canBeLeftOpenTag</span>: options.canBeLeftOpenTag,</span><br><span class="line">    <span class="attr">shouldDecodeNewlines</span>: options.shouldDecodeNewlines,</span><br><span class="line">    <span class="attr">shouldDecodeNewlinesForHref</span>: options.shouldDecodeNewlinesForHref,</span><br><span class="line">    <span class="attr">shouldKeepComment</span>: options.comments,</span><br><span class="line">    <span class="comment">// 把模板字符串中不同的内容出来之后，这4个钩子函数把提取出来的内容生成对应的AST</span></span><br><span class="line">    <span class="comment">// 具体的代码，还是去看源码里面吧</span></span><br><span class="line">    <span class="comment">// 当解析到开始标签时，调用该函数</span></span><br><span class="line">    start (tag, attrs, unary) &#123;</span><br><span class="line"></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 当解析到结束标签时，调用该函数</span></span><br><span class="line">    end () &#123;</span><br><span class="line"></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 当解析到文本时，调用该函数</span></span><br><span class="line">    chars (text) &#123;</span><br><span class="line"></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// 当解析到注释时，调用该函数</span></span><br><span class="line">    comment (text) &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="keyword">return</span> root</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面代码中可以看到，<code>parse</code> 函数就是解析器的主函数，在<code>parse</code> 函数内调用了<code>parseHTML</code> 函数对模板字符串进行解析，在<code>parseHTML</code> 函数解析模板字符串的过程中，如果遇到文本信息，就会调用文本解析器<code>parseText</code>函数进行文本解析；如果遇到文本中包含过滤器，就会调用过滤器解析器<code>parseFilters</code>函数进行解析</p>
<table>
<thead>
<tr>
<th>type取值</th>
<th>对应的AST节点类型</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>元素节点</td>
</tr>
<tr>
<td>2</td>
<td>包含变量的动态文本节点</td>
</tr>
<tr>
<td>3</td>
<td>不包含变量的纯文本节点</td>
</tr>
</tbody></table>
<h5 id="HTML解析器"><a href="#HTML解析器" class="headerlink" title="HTML解析器"></a>HTML解析器</h5><p><code>HTML</code>解析器主要负责解析出模板字符串中有哪些内容，然后根据不同的内容才能调用其他的解析器以及做相应的处理</p>
<p>一边解析不同的内容一边调用对应的钩子函数生成对应的<code>AST</code>节点，最终完成将整个模板字符串转化成<code>AST</code>,这就是<code>HTML</code>解析器所要做的工作</p>
<p>后面的就是通过根据模板里的内容的特点使用不同的正则和其他方法进行解析出不同的内容，如注释，条件注释，DOCTYPE，开始标签（标签属性，是都自闭合），结束标签，文本</p>
<p>通过在解析的过程中维护一个保存开始标签的栈配合遇到结束标签时的推出栈顶元素保证AST节点的层级关系</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parseHTML</span>(<span class="params">html, options</span>) </span>&#123;</span><br><span class="line">	<span class="keyword">var</span> stack = [];</span><br><span class="line">	<span class="keyword">var</span> expectHTML = options.expectHTML;</span><br><span class="line">	<span class="keyword">var</span> isUnaryTag$$1 = options.isUnaryTag || no;</span><br><span class="line">	<span class="keyword">var</span> canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;</span><br><span class="line">	<span class="keyword">var</span> index = <span class="number">0</span>;</span><br><span class="line">	<span class="keyword">var</span> last, lastTag;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// 开启一个 while 循环，循环结束的条件是 html 为空，即 html 被 parse 完毕</span></span><br><span class="line">	<span class="keyword">while</span> (html) &#123;</span><br><span class="line">		last = html;</span><br><span class="line">		<span class="comment">// 确保即将 parse 的内容不是在纯文本标签里 (script,style,textarea)</span></span><br><span class="line">		<span class="keyword">if</span> (!lastTag || !isPlainTextElement(lastTag)) &#123;</span><br><span class="line">		   <span class="keyword">let</span> textEnd = html.indexOf(<span class="string">&#x27;&lt;&#x27;</span>)</span><br><span class="line">              <span class="comment">/**</span></span><br><span class="line"><span class="comment">               * 如果html字符串是以&#x27;&lt;&#x27;开头,则有以下几种可能</span></span><br><span class="line"><span class="comment">               * 开始标签:&lt;div&gt;</span></span><br><span class="line"><span class="comment">               * 结束标签:&lt;/div&gt;</span></span><br><span class="line"><span class="comment">               * 注释:&lt;!-- 我是注释 --&gt;</span></span><br><span class="line"><span class="comment">               * 条件注释:&lt;!-- [if !IE] --&gt; &lt;!-- [endif] --&gt;</span></span><br><span class="line"><span class="comment">               * DOCTYPE:&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="comment">               * 需要一一去匹配尝试</span></span><br><span class="line"><span class="comment">               */</span></span><br><span class="line">            <span class="keyword">if</span> (textEnd === <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 里面具体的代码就不展示了</span></span><br><span class="line">                <span class="comment">// 解析是否是注释</span></span><br><span class="line">        		<span class="keyword">if</span> (comment.test(html)) &#123;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 解析是否是条件注释</span></span><br><span class="line">                <span class="keyword">if</span> (conditionalComment.test(html)) &#123;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 解析是否是DOCTYPE</span></span><br><span class="line">                <span class="keyword">const</span> doctypeMatch = html.match(doctype)</span><br><span class="line">                <span class="keyword">if</span> (doctypeMatch) &#123;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 解析是否是结束标签</span></span><br><span class="line">                <span class="keyword">const</span> endTagMatch = html.match(endTag)</span><br><span class="line">                <span class="keyword">if</span> (endTagMatch) &#123;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">// 匹配是否是开始标签</span></span><br><span class="line">                <span class="keyword">const</span> startTagMatch = parseStartTag()</span><br><span class="line">                <span class="keyword">if</span> (startTagMatch) &#123;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 如果html字符串不是以&#x27;&lt;&#x27;开头,则解析文本类型</span></span><br><span class="line">            <span class="keyword">let</span> text, rest, next</span><br><span class="line">            <span class="keyword">if</span> (textEnd &gt;= <span class="number">0</span>) &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 如果在html字符串中没有找到&#x27;&lt;&#x27;，表示这一段html字符串都是纯文本</span></span><br><span class="line">            <span class="keyword">if</span> (textEnd &lt; <span class="number">0</span>) &#123;</span><br><span class="line">                text = html</span><br><span class="line">                html = <span class="string">&#x27;&#x27;</span></span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 把截取出来的text转化成textAST</span></span><br><span class="line">            <span class="keyword">if</span> (options.chars &amp;&amp; text) &#123;</span><br><span class="line">                options.chars(text)</span><br><span class="line">            &#125;</span><br><span class="line">		&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="comment">// 父元素为script、style、textarea时，其内部的内容全部当做纯文本处理</span></span><br><span class="line">		&#125;</span><br><span class="line"></span><br><span class="line">		<span class="comment">//将整个字符串作为文本对待</span></span><br><span class="line">		<span class="keyword">if</span> (html === last) &#123;</span><br><span class="line">			options.chars &amp;&amp; options.chars(html);</span><br><span class="line">			<span class="keyword">if</span> (!stack.length &amp;&amp; options.warn) &#123;</span><br><span class="line">				options.warn((<span class="string">&quot;Mal-formatted tag at end of template: \&quot;&quot;</span> + html + <span class="string">&quot;\&quot;&quot;</span>));</span><br><span class="line">			&#125;</span><br><span class="line">			<span class="keyword">break</span></span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Clean up any remaining tags</span></span><br><span class="line">	parseEndTag();</span><br><span class="line">	<span class="comment">//parse 开始标签</span></span><br><span class="line">	<span class="function"><span class="keyword">function</span> <span class="title">parseStartTag</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">//处理 parseStartTag 的结果</span></span><br><span class="line">	<span class="function"><span class="keyword">function</span> <span class="title">handleStartTag</span>(<span class="params">match</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">//parse 结束标签</span></span><br><span class="line">	<span class="function"><span class="keyword">function</span> <span class="title">parseEndTag</span>(<span class="params">tagName, start, end</span>) </span>&#123;</span><br><span class="line"></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="https://vue-js.com/learn-vue/complie/HTMLParse.html#_5-%E5%9B%9E%E5%BD%92%E6%BA%90%E7%A0%81">大佬的解析</a></p>
<hr>
<h5 id="文本解析器"><a href="#文本解析器" class="headerlink" title="文本解析器"></a>文本解析器</h5><p>当<code>HTML</code>解析器解析到文本内容时会调用4个钩子函数中的<code>chars</code>函数来创建文本型的<code>AST</code>节点，并且也说了在<code>chars</code>函数中会根据文本内容是否包含变量再细分为创建含有变量的<code>AST</code>节点和不包含变量的<code>AST</code>节点</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 当解析到标签的文本时，触发chars</span></span><br><span class="line">chars (text) &#123;</span><br><span class="line">  <span class="comment">// parseText()文本解析器</span></span><br><span class="line">  <span class="keyword">if</span>(res = parseText(text))&#123;</span><br><span class="line">       <span class="comment">// 包含变量</span></span><br><span class="line">       <span class="keyword">let</span> element = &#123;</span><br><span class="line">           <span class="attr">type</span>: <span class="number">2</span>,</span><br><span class="line">           <span class="attr">expression</span>: res.expression,</span><br><span class="line">           <span class="attr">tokens</span>: res.tokens,</span><br><span class="line">           text</span><br><span class="line">       &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">       <span class="comment">// 不包含变量，纯文本节点</span></span><br><span class="line">       <span class="keyword">let</span> element = &#123;</span><br><span class="line">           <span class="attr">type</span>: <span class="number">3</span>,</span><br><span class="line">           text</span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>假设现有由<code>HTML</code>解析器解析得到的文本内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> text = <span class="string">&quot;我叫&#123;&#123;name&#125;&#125;，我今年&#123;&#123;age&#125;&#125;岁了&quot;</span></span><br></pre></td></tr></table></figure>

<p>经过文本解析器解析后得到：</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">let</span> res = parseText(text)</span><br><span class="line">res = &#123;</span><br><span class="line">    <span class="attr">expression</span>:<span class="string">&quot;我叫&quot;</span>+_s(name)+<span class="string">&quot;，我今年&quot;</span>+_s(age)+<span class="string">&quot;岁了&quot;</span>,</span><br><span class="line">    <span class="attr">tokens</span>:[</span><br><span class="line">        <span class="string">&quot;我叫&quot;</span>,</span><br><span class="line">        &#123;<span class="string">&#x27;@binding&#x27;</span>: name &#125;,</span><br><span class="line">        <span class="string">&quot;，我今年&quot;</span></span><br><span class="line">        &#123;<span class="string">&#x27;@binding&#x27;</span>: age &#125;,</span><br><span class="line">    	<span class="string">&quot;岁了&quot;</span></span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>expression</code>属性就是把文本中的变量和非变量提取出来，然后把变量用<code>_s()</code>包裹，最后按照文本里的顺序把它们用<code>+</code>连接起来。而<code>tokens</code>是个数组，数组内容也是文本中的变量和非变量，不一样的是把变量构造成<code>&#123;&#39;@binding&#39;: xxx&#125;</code>。</p>
<p>现在我们就可以知道文本解析器内部就干了三件事：</p>
<ul>
<li>判断传入的文本是否包含变量</li>
<li>构造expression</li>
<li>构造tokens</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> defaultTagRE = <span class="regexp">/\&#123;\&#123;((?:.|\n)+?)\&#125;\&#125;/g</span></span><br><span class="line"><span class="keyword">const</span> buildRegex = cached(<span class="function"><span class="params">delimiters</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> open = delimiters[<span class="number">0</span>].replace(regexEscapeRE, <span class="string">&#x27;\\$&amp;&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> close = delimiters[<span class="number">1</span>].replace(regexEscapeRE, <span class="string">&#x27;\\$&amp;&#x27;</span>)</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">RegExp</span>(open + <span class="string">&#x27;((?:.|\\n)+?)&#x27;</span> + close, <span class="string">&#x27;g&#x27;</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">parseText</span> (<span class="params">text,delimiters</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE</span><br><span class="line">  <span class="keyword">if</span> (!tagRE.test(text)) &#123;</span><br><span class="line">    <span class="keyword">return</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">const</span> tokens = []</span><br><span class="line">  <span class="keyword">const</span> rawTokens = []</span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * let lastIndex = tagRE.lastIndex = 0</span></span><br><span class="line"><span class="comment">   * 上面这行代码等同于下面这两行代码:</span></span><br><span class="line"><span class="comment">   * tagRE.lastIndex = 0</span></span><br><span class="line"><span class="comment">   * let lastIndex = tagRE.lastIndex</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">let</span> lastIndex = tagRE.lastIndex = <span class="number">0</span></span><br><span class="line">  <span class="keyword">let</span> match, index, tokenValue</span><br><span class="line">  <span class="keyword">while</span> ((match = tagRE.exec(text))) &#123;</span><br><span class="line">    index = match.index</span><br><span class="line">    <span class="comment">// push text token</span></span><br><span class="line">    <span class="keyword">if</span> (index &gt; lastIndex) &#123;</span><br><span class="line">      <span class="comment">// 先把&#x27;&#123;&#123;&#x27;前面的文本放入tokens中</span></span><br><span class="line">      rawTokens.push(tokenValue = text.slice(lastIndex, index))</span><br><span class="line">      tokens.push(<span class="built_in">JSON</span>.stringify(tokenValue))</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// tag token</span></span><br><span class="line">    <span class="comment">// 取出&#x27;&#123;&#123; &#125;&#125;&#x27;中间的变量exp</span></span><br><span class="line">    <span class="keyword">const</span> exp = parseFilters(match[<span class="number">1</span>].trim())</span><br><span class="line">    <span class="comment">// 把变量exp改成_s(exp)形式也放入tokens中</span></span><br><span class="line">    tokens.push(<span class="string">`_s(<span class="subst">$&#123;exp&#125;</span>)`</span>)</span><br><span class="line">    rawTokens.push(&#123; <span class="string">&#x27;@binding&#x27;</span>: exp &#125;)</span><br><span class="line">    <span class="comment">// 设置lastIndex 以保证下一轮循环时，只从&#x27;&#125;&#125;&#x27;后面再开始匹配正则</span></span><br><span class="line">    lastIndex = index + match[<span class="number">0</span>].length</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 当剩下的text不再被正则匹配上时，表示所有变量已经处理完毕</span></span><br><span class="line">  <span class="comment">// 此时如果lastIndex &lt; text.length，表示在最后一个变量后面还有文本</span></span><br><span class="line">  <span class="comment">// 最后将后面的文本再加入到tokens中</span></span><br><span class="line">  <span class="keyword">if</span> (lastIndex &lt; text.length) &#123;</span><br><span class="line">    rawTokens.push(tokenValue = text.slice(lastIndex))</span><br><span class="line">    tokens.push(<span class="built_in">JSON</span>.stringify(tokenValue))</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 最后把数组tokens中的所有元素用&#x27;+&#x27;拼接起来</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">expression</span>: tokens.join(<span class="string">&#x27;+&#x27;</span>),</span><br><span class="line">    <span class="attr">tokens</span>: rawTokens</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<h5 id="优化阶段"><a href="#优化阶段" class="headerlink" title="优化阶段"></a>优化阶段</h5><p>模板编译的最终目的是用模板生成一个<code>render</code>函数，而用<code>render</code>函数就可以生成与模板对应的<code>VNode</code>，之后再进行<code>patch</code>算法，最后完成视图渲染。我们可以在<code>patch</code>过程中不用去对比这些静态节点了，这样就可以提高一些性能</p>
<p>优化阶段其实就干了两件事：</p>
<ol>
<li>在<code>AST</code>中找出所有静态节点并打上标记；</li>
<li>在<code>AST</code>中找出所有静态根节点并打上标记；</li>
</ol>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// src/compiler/optimizer.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">optimize</span> (<span class="params">root: ?ASTElement, options: CompilerOptions</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (!root) <span class="keyword">return</span></span><br><span class="line">  isStaticKey = genStaticKeysCached(options.staticKeys || <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  isPlatformReservedTag = options.isReservedTag || no</span><br><span class="line">  <span class="comment">// 标记静态节点</span></span><br><span class="line">  markStatic(root)</span><br><span class="line">  <span class="comment">// 标记静态根节点</span></span><br><span class="line">  markStaticRoots(root, <span class="literal">false</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>标记静态节点</strong></p>
<p>我们只需从根节点开始，先标记根节点是否为静态节点，然后看根节点如果是元素节点，那么就去向下递归它的子节点，子节点如果还有子节点那就继续向下递归，直到标记完所有节点。</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">node.pre ||</span><br><span class="line">(</span><br><span class="line">    !node.hasBindings &amp;&amp; <span class="comment">// no dynamic bindings</span></span><br><span class="line">    !node.if &amp;&amp; !node.for &amp;&amp; <span class="comment">// not v-if or v-for or v-else</span></span><br><span class="line">    !isBuiltInTag(node.tag) &amp;&amp; <span class="comment">// not a built-in</span></span><br><span class="line">    isPlatformReservedTag(node.tag) &amp;&amp; <span class="comment">// not a component</span></span><br><span class="line">    !isDirectChildOfTemplateFor(node) &amp;&amp;</span><br><span class="line">    <span class="built_in">Object</span>.keys(node).every(isStaticKey)</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p>如果元素节点是静态节点，那就必须满足以下几点要求：</p>
<ul>
<li><p>如果节点使用了<code>v-pre</code>指令，那就断定它是静态节点；</p>
</li>
<li><p>如果节点没有使用</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">v-pre	// 跳过此节点和子节点的编译过程</span><br></pre></td></tr></table></figure>

<p>指令，那它要成为静态节点必须满足：</p>
<ul>
<li>不能使用动态绑定语法，即标签上不能有<code>v-</code>、<code>@</code>、<code>:</code>开头的属性；</li>
<li>不能使用<code>v-if</code>、<code>v-else</code>、<code>v-for</code>指令；</li>
<li>不能是内置组件，即标签名不能是<code>slot</code>和<code>component</code>；</li>
<li>标签名必须是平台保留标签，即不能是组件；</li>
<li>当前节点的父节点不能是带有 <code>v-for</code> 的 <code>template</code> 标签；</li>
<li>节点的所有属性的 <code>key</code> 都必须是静态节点才有的 <code>key</code>，注：静态节点的<code>key</code>是有限的，它只能是<code>type</code>,<code>tag</code>,<code>attrsList</code>,<code>attrsMap</code>,<code>plain</code>,<code>parent</code>,<code>children</code>,<code>attrs</code>之一；</li>
</ul>
</li>
</ul>
<p>标记完当前节点是否为静态节点之后，如果该节点是元素节点，那么还要继续去递归判断它的子节点</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = node.children.length; i &lt; l; i++) &#123;</span><br><span class="line">    <span class="keyword">const</span> child = node.children[i]</span><br><span class="line">    markStatic(child)</span><br><span class="line">    <span class="keyword">if</span> (!child.static) &#123;</span><br><span class="line">        <span class="comment">// 当有一个子节点不是静态节点，则当前节点也不是静态节点</span></span><br><span class="line">        node.static = <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>标记静态根节点</strong></p>
<p>一个节点要想成为静态根节点，它必须满足以下要求：</p>
<ul>
<li>节点本身必须是静态节点；</li>
<li>必须拥有子节点 <code>children</code>；</li>
<li>子节点不能只是只有一个文本节点；</li>
</ul>
<p>否则的话，对它的优化成本将大于优化后带来的收益。</p>
<p>从<code>AST</code>根节点递归向下遍历寻找</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">markStaticRoots</span> (<span class="params">node: ASTNode, isInFor: boolean</span>) </span>&#123;</span><br><span class="line">   <span class="comment">// 元素节点</span></span><br><span class="line">  <span class="keyword">if</span> (node.type === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (node.static || node.once) &#123;</span><br><span class="line">      node.staticInFor = isInFor</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// For a node to qualify as a static root, it should have children that</span></span><br><span class="line">    <span class="comment">// are not just static text. Otherwise the cost of hoisting out will</span></span><br><span class="line">    <span class="comment">// outweigh the benefits and it&#x27;s better off to just always render it fresh.</span></span><br><span class="line">    <span class="comment">// 主要是这里的判断</span></span><br><span class="line">    <span class="keyword">if</span> (node.static &amp;&amp; node.children.length &amp;&amp; !(</span><br><span class="line">      node.children.length === <span class="number">1</span> &amp;&amp;</span><br><span class="line">      node.children[<span class="number">0</span>].type === <span class="number">3</span></span><br><span class="line">    )) &#123;</span><br><span class="line">      node.staticRoot = <span class="literal">true</span></span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      node.staticRoot = <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (node.children) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>, l = node.children.length; i &lt; l; i++) &#123;</span><br><span class="line">        markStaticRoots(node.children[i], isInFor || !!node.for)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (node.ifConditions) &#123;</span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">1</span>, l = node.ifConditions.length; i &lt; l; i++) &#123;</span><br><span class="line">        markStaticRoots(node.ifConditions[i].block, isInFor)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<h5 id="代码生成阶段"><a href="#代码生成阶段" class="headerlink" title="代码生成阶段"></a>代码生成阶段</h5><p>所谓代码生成阶段，到底是要生成什么代码？答：要生成<code>render</code>函数字符串。</p>
<p>其实就是根据模板对应的抽象语法树<code>AST</code>生成一个函数，通过调用这个函数就可以得到模板对应的虚拟<code>DOM</code></p>
<p><strong>如何生成</strong></p>
<p>递归的根据AST的节点和子节点调用相应的函数创建VNode</p>
<figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 主要流程</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">generate</span> (<span class="params">ast,option</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> state = <span class="keyword">new</span> CodegenState(options)</span><br><span class="line">  <span class="keyword">const</span> code = ast ? genElement(ast, state) : <span class="string">&#x27;_c(&quot;div&quot;)&#x27;</span></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">render</span>: <span class="string">`with(this)&#123;return <span class="subst">$&#123;code&#125;</span>&#125;`</span>,</span><br><span class="line">    <span class="attr">staticRenderFns</span>: state.staticRenderFns</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 核心函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">genElement</span> (<span class="params">el: ASTElement, state: CodegenState</span>): <span class="title">string</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (el.staticRoot &amp;&amp; !el.staticProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genStatic(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.once &amp;&amp; !el.onceProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genOnce(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.for &amp;&amp; !el.forProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genFor(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.if &amp;&amp; !el.ifProcessed) &#123;</span><br><span class="line">    <span class="keyword">return</span> genIf(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;template&#x27;</span> &amp;&amp; !el.slotTarget) &#123;</span><br><span class="line">    <span class="keyword">return</span> genChildren(el, state) || <span class="string">&#x27;void 0&#x27;</span></span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (el.tag === <span class="string">&#x27;slot&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> genSlot(el, state)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// component or element</span></span><br><span class="line">    <span class="keyword">let</span> code</span><br><span class="line">    <span class="keyword">if</span> (el.component) &#123;</span><br><span class="line">      code = genComponent(el.component, el, state)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> data = el.plain ? <span class="literal">undefined</span> : genData(el, state)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> children = el.inlineTemplate ? <span class="literal">null</span> : genChildren(el, state, <span class="literal">true</span>)</span><br><span class="line">      code = <span class="string">`_c(&#x27;<span class="subst">$&#123;el.tag&#125;</span>&#x27;<span class="subst">$&#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">        data ? <span class="string">`,<span class="subst">$&#123;data&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> data</span></span></span><br><span class="line"><span class="subst"><span class="string">      &#125;</span><span class="subst">$&#123;</span></span></span><br><span class="line"><span class="subst"><span class="string">        children ? <span class="string">`,<span class="subst">$&#123;children&#125;</span>`</span> : <span class="string">&#x27;&#x27;</span> <span class="regexp">//</span> children</span></span></span><br><span class="line"><span class="subst"><span class="string">      &#125;</span>)`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// module transforms</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; state.transforms.length; i++) &#123;</span><br><span class="line">      code = state.transforms[i](el, code)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> code</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其实在后面还有一个对模板编译整体流程的解析，但是有些绕，可以去看一下</p>
<p><a target="_blank" rel="noopener" href="https://vue-js.com/learn-vue/complie/summary.html#_1-%E5%89%8D%E8%A8%80">整体解析地址</a></p>
<hr>
<h4 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h4><img src="https://vue-js.com/learn-vue/assets/img/1.6e1e57be.jpg" alt="img" style="zoom:67%;" />

<h5 id="初始阶段-new-Vue"><a href="#初始阶段-new-Vue" class="headerlink" title="初始阶段(new Vue)"></a>初始阶段(new Vue)</h5></div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="mailto:undefined">晟松</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://chengsong.info/2020/11/16/vue源码简单解析/">https://chengsong.info/2020/11/16/vue源码简单解析/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://chengsong.info">晟松</a>！</span></div></div><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/vue2/">vue2</a></div><nav id="pagination"><div class="prev-post pull-left"><a href="/2020/11/21/linux%E7%9F%A5%E8%AF%86%E7%82%B9%E5%A4%8D%E4%B9%A0/"><i class="fa fa-chevron-left">  </i><span>linux知识点复习</span></a></div><div class="next-post pull-right"><a href="/2020/10/29/vue3%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8/"><span>vue3简单使用</span><i class="fa fa-chevron-right"></i></a></div></nav></div></div><footer class="footer-bg" style="background-image: url(/img/blog-bg.jpg)"><div class="layout" id="footer"><div class="copyright">&copy;2020 - 2023 By 晟松</div><div class="framework-info"><span>驱动 - </span><a target="_blank" rel="noopener" href="http://hexo.io"><span>Hexo</span></a><span class="footer-separator">|</span><span>主题 - </span><a target="_blank" rel="noopener" href="https://github.com/Molunerfinn/hexo-theme-melody"><span>Melody</span></a></div><div class="icp"><a target="_blank" rel="noopener" href="http://www.beian.miit.gov.cn/"><span>湘ICP备2020021729号</span></a></div><div class="busuanzi"><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><span id="busuanzi_container_page_pv"><i class="fa fa-file"></i><span id="busuanzi_value_page_pv"></span><span></span></span></div></div></footer><i class="fa fa-arrow-up" id="go-up" aria-hidden="true"></i><script src="https://cdn.jsdelivr.net/npm/animejs@latest/anime.min.js"></script><script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/fancybox@latest/dist/jquery.fancybox.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-animate@latest/velocity.min.js"></script><script src="https://cdn.jsdelivr.net/npm/velocity-ui-pack@latest/velocity.ui.min.js"></script><script src="/js/utils.js?version=1.9.0"></script><script src="/js/fancybox.js?version=1.9.0"></script><script src="/js/sidebar.js?version=1.9.0"></script><script src="/js/copy.js?version=1.9.0"></script><script src="/js/fireworks.js?version=1.9.0"></script><script src="/js/transition.js?version=1.9.0"></script><script src="/js/scroll.js?version=1.9.0"></script><script src="/js/head.js?version=1.9.0"></script><script src="/js/search/local-search.js"></script><script>if(/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
  $('#nav').addClass('is-mobile')
  $('footer').addClass('is-mobile')
  $('#top-container').addClass('is-mobile')
}</script><div class="search-dialog" id="local-search"><div class="search-dialog__title" id="local-search-title">本地搜索</div><div id="local-input-panel"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章"></div></div></div><hr><div id="local-search-results"><div id="local-hits"></div><div id="local-stats"><div class="local-search-stats__hr" id="hr"><span>由</span> <a target="_blank" rel="noopener" href="https://github.com/wzpan/hexo-generator-search" style="color:#49B1F5;">hexo-generator-search</a>
 <span>提供支持</span></div></div></div><span class="search-close-button"><i class="fa fa-times"></i></span></div><div class="search-mask"></div></body></html>